package com.lyx.question.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.lyx.common.constant.QuestionRecordConstant;
import com.lyx.common.constant.RedisContant;
import com.lyx.common.exception.BusinessException;
import com.lyx.common.exception.ExceptionEnum;
import com.lyx.common.result.R;
import com.lyx.common.to.UserTo;
import com.lyx.common.utils.JwtUtils;
import com.lyx.question.dto.PaperWitnQuestionDto;
import com.lyx.question.dto.QuestionAnswerDto;
import com.lyx.question.dto.QuestionDto;
import com.lyx.question.dto.RankDto;
import com.lyx.question.entity.Paper;
import com.lyx.question.entity.Question;
import com.lyx.question.entity.QuestionRecord;
import com.lyx.question.feign.UserFeignService;
import com.lyx.question.mapper.PaperMapper;
import com.lyx.question.mapper.QuestionMapper;
import com.lyx.question.mapper.QuestionRecordMapper;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author 14667
 * @date 2022/7/26 9:47
 */
@Component
public class QuestionRecordServiceImpl extends ServiceImpl<QuestionRecordMapper, QuestionRecord> implements com.lyx.question.service.QuestionRecordService {

    @Autowired
    PaperMapper paperMapper;

    @Autowired
    QuestionMapper questionMapper;

    @Autowired
    StringRedisTemplate redisTemplate;

    @Autowired
    UserFeignService userFeignService;
    @Override
    public List<Paper> indexQuestionRecord(HttpServletRequest request) {
        //这里不在需要校验了  因为在网关层已经统一进行了校验，能到这id肯定是正确的了
        String id = request.getHeader(RedisContant.USER_ID);
        Integer userId = Integer.parseInt(id);
        List<Paper> papers = paperMapper.selectList(new QueryWrapper<Paper>().eq("user_id", userId)
                .orderByDesc("update_time")
                .last("limit " + QuestionRecordConstant.INDEX_QUESTION_RECORD_LIMIT));
        return papers;
    }

    @Override
    public Map<String, List<Paper>> allQuestionRecord(HttpServletRequest request) {
        //这里不在需要校验了  因为在网关层已经统一进行了校验，能到这id肯定是正确的了
        String id = request.getHeader(RedisContant.USER_ID);
        Integer userId = Integer.parseInt(id);
        List<Paper> paperList = paperMapper.selectList((new QueryWrapper<Paper>().eq("user_id", userId).orderByDesc("commit_time")));
        Map<String, List<Paper>> collect = paperList.stream().collect(Collectors.groupingBy(paper -> {
            return paper.getCommitTime().toLocalDate().toString();
        }));
        TreeMap<String, List<Paper>> stringListTreeMap = new TreeMap<>(new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return LocalDate.parse(o2).compareTo(LocalDate.parse(o1));
            }
        });
        stringListTreeMap.putAll(collect);

        return stringListTreeMap;
    }

    @Override
    public List<RankDto> questionRecordRank() {
        //key为时间，value为用户，socre为次数,将最近7天的值加起来组成一个新的键
        java.time.LocalDate day1 = java.time.LocalDate.now();
        Set<ZSetOperations.TypedTuple<String>> collect = redisTemplate.opsForZSet().reverseRangeWithScores(RedisContant.QUESTION_NUM_WEEK_RANK + "_" + day1.toString(), 0, 49);
        //这是为了后面给对应的用户赋值分数
        ImmutableMap<String, ZSetOperations.TypedTuple<String>> collectMap = Maps.uniqueIndex(collect, ZSetOperations.TypedTuple::getValue);

        //得到他们的id，然后去用户表查寻名字
        List<Integer> userIds = collect.stream().filter(item->Integer.parseInt(item.getValue())>0).map(item -> {
            return Integer.parseInt(item.getValue());
        }).collect(Collectors.toList());
        //查询到对应的用户，进行类型转换
        try{
            R users = userFeignService.getUsers(userIds);
            if(users.getSuccess().equals("false")){
                throw new BusinessException(ExceptionEnum.USERS_INFO_ERROR);
            }
            Map<String, Object> data = users.getData();
            List<UserTo> userTos = JSONArray.parseArray(JSON.toJSONString(data.get("data")), UserTo.class);
            List<RankDto> rankDtoList = new ArrayList<>();
            //组装返回的数据
            for(int i = 0;i<userTos.size();i++){
                RankDto rankDto = new RankDto();
                rankDto.setScore(collectMap.get(Objects.toString(userTos.get(i).getId())).getScore());
                rankDto.setUsername(userTos.get(i).getUserName());
                rankDtoList.add(rankDto);
            }
            List<RankDto> collect1 = rankDtoList.stream().sorted(Comparator.comparing(RankDto::getScore, Comparator.reverseOrder()).thenComparing(RankDto::getUsername)).collect(Collectors.toList());
            return collect1;
        }catch(Exception e){
            e.printStackTrace();
            throw new BusinessException(ExceptionEnum.SERVICE_UNAVAILABLE);
        }
    }

    @Override
    public void updateQuestionRecord(PaperWitnQuestionDto paperWitnQuestionDto, HttpServletRequest request) {
        //这里不在需要校验了  因为在网关层已经统一进行了校验，能到这id肯定是正确的了
        String id = request.getHeader(RedisContant.USER_ID);
        Integer userId = Integer.parseInt(id);
        if(Objects.isNull(paperWitnQuestionDto)){
            throw new BusinessException(ExceptionEnum.IS_NOT_NULL.getCode(),String.format(ExceptionEnum.IS_NOT_NULL.getMsg(),"答题记录为空"));
        }
        Integer userId1= paperWitnQuestionDto.getUserId();
        //如果解析出来的用户和本地传过来的id不一致，说明用户信息出错
        if(!userId1.equals(userId)){
            throw new BusinessException(ExceptionEnum.USER_NOT_LOGIN);
        }
        System.out.println("updateQuestionRecord2-----------------------");
        redisTemplate.opsForHash().put(RedisContant.QUESTION_RECORD_CACHE,
                        paperWitnQuestionDto.getUserId().toString() + "_" + paperWitnQuestionDto.getPaperId().toString(),
                        JSON.toJSONString(paperWitnQuestionDto));

    }

    @Override
    public PaperWitnQuestionDto getCurrentPaperQuestion(Integer paperId, HttpServletRequest request) {
        //这里不在需要校验了  因为在网关层已经统一进行了校验，能到这id肯定是正确的了
        String id = request.getHeader(RedisContant.USER_ID);
        Integer userId = Integer.parseInt(id);
        //根据试卷的完成情况，判断用户是否可以进入该界面
        Paper paper = paperMapper.selectById(paperId);
        if (Objects.isNull(paper)){
            throw new BusinessException(ExceptionEnum.NOT_EXIST.getCode(),String.format(ExceptionEnum.NOT_EXIST.getMsg(),"试卷"));
        }
        if ((paper.getFinishStatus().equals(1))){
            throw new BusinessException(ExceptionEnum.PAPER_FINISH);
        }
        //查询缓存中是否有试卷的信息，有的话直接返回
        Object o = redisTemplate.opsForHash().get(RedisContant.QUESTION_RECORD_CACHE,
                userId.toString() + "_" + paperId.toString());
        if(Objects.nonNull(o)){
            PaperWitnQuestionDto paperWitnQuestionDto = JSON.parseObject((String) o, PaperWitnQuestionDto.class);
            return  paperWitnQuestionDto;
        }
        //没有缓存的话查询数据库
        List<QuestionRecord> questionRecordList = baseMapper.selectList(new QueryWrapper<QuestionRecord>().eq("paper_id", paperId));
        List<QuestionDto> questionDtoList = questionRecordList.stream().map(questionRecord -> {
            QuestionDto questionDto = new QuestionDto();
            BeanUtils.copyProperties(questionRecord, questionDto);
            return questionDto;
        }).collect(Collectors.toList());
        PaperWitnQuestionDto paperWitnQuestionDto = new PaperWitnQuestionDto();
        paperWitnQuestionDto.setQuestionDtoList(questionDtoList);
        paperWitnQuestionDto.setCreateTime(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(paper.getCommitTime().minusMinutes(paper.getTotalTime())));
        paperWitnQuestionDto.setPaperId(paperId);
        paperWitnQuestionDto.setCommitTime(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(paper.getCommitTime()));
        paperWitnQuestionDto.setUserId(userId);
        //放入缓存
        redisTemplate.opsForHash().put(RedisContant.QUESTION_RECORD_CACHE,
                userId.toString() + "_" + paperId.toString(),
                JSON.toJSONString(paperWitnQuestionDto));
        return paperWitnQuestionDto;
    }

    @Override
    public QuestionAnswerDto comfirmAnswerQuestion(HttpServletRequest request, Integer questionId, String userAnswer) {
        //这里不在需要校验了  因为在网关层已经统一进行了校验，能到这id肯定是正确的了
        String id = request.getHeader(RedisContant.USER_ID);
        Integer userId = Integer.parseInt(id);
        if(Objects.isNull(userAnswer) || userAnswer.equals("")){
            throw new BusinessException(ExceptionEnum.NO_USER_ANSWER);
        }
        //查出题目相关信息
        Question question = questionMapper.selectById(questionId);
        //生成做题记录并保存
        QuestionRecord questionRecord = new QuestionRecord();
        questionRecord.setQuestionType(question.getQuestionType());
        questionRecord.setQuestionId(questionId);
        questionRecord.setUserAnswer(userAnswer);
        questionRecord.setDoneStatus(QuestionRecordConstant.DONE_STATUS);
        questionRecord.setPaperId(QuestionRecordConstant.ES_QUESTION_RECORD_PAPER_ID);
        questionRecord.setUserId(userId);
        questionRecord.setChapterId(question.getChapterId());
        questionRecord.setQuestionDescription(question.getQuestionDescription());
        questionRecord.setChoices(question.getChoices());
        questionRecord.setNumber(1);
        int insert = baseMapper.insert(questionRecord);
        if (insert<=0){
            throw new BusinessException(ExceptionEnum.QUESTION_RECORD_SAVE_ERROR);
        }
        //TODO 更新排行榜
        LocalDate now = LocalDate.now();
        Double aDouble = redisTemplate.opsForZSet().incrementScore(now.toString(), userId.toString(), 1);
        redisTemplate.expire(now.toString(),7, TimeUnit.DAYS);
        //返回答案解析等信息
        QuestionAnswerDto questionAnswerDto = new QuestionAnswerDto();
        questionAnswerDto.setAnswer(question.getAnswer());
        questionAnswerDto.setQuestionAnalysis(question.getQuestionAnalysis());
        questionAnswerDto.setUserAnswer(userAnswer);
        return questionAnswerDto;
    }

    public static List<UserTo> objToList(Object obj) {
        List<UserTo> result = new ArrayList<UserTo>();
        try {
            if (obj instanceof ArrayList<?>) {
                for (Object o : (List<?>) obj) {
                    UserTo  json = JSONObject.parseObject(JSON.toJSONString(o), UserTo.class);
                    result.add(json);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

}
